﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Security;
using Meraz.Core.Factories;
using Meraz.Core.Model;
using Meraz.Core.Repositories;

using Ninject;
using System.Web;
using Meraz.Core.Data;
using System.Configuration.Provider;

namespace Meraz.Core.Providers
{
    public interface ICustomRoleProvider
    {
        IRoleRepository RoleRepository { get; set; }
        IMemberRoleRepository MemberRoleRepository { get; set; }
        void AddUsersToRoles(string[] usernames, string[] roleNames);
        string ApplicationName { get; set; }
        void CreateRole(string roleName);
        bool DeleteRole(string roleName, bool throwOnPopulatedRole);
        string[] FindUsersInRole(string roleName, string usernameToMatch);
        string[] GetAllRoles();
        string[] GetRolesForUser(string username);
        string[] GetUsersInRole(string roleName);
        bool IsUserInRole(string username, string roleName);
        void RemoveUsersFromRoles(string[] usernames, string[] roleNames);
        bool RoleExists(string roleName);
    }
    public class CustomRoleProvider : RoleProvider, ICustomRoleProvider
    {
        string name = null;
        public IRoleRepository RoleRepository { get; set; }
        public IMemberRoleRepository MemberRoleRepository { get; set; }
        public IMemberRepository MemberRepository { get; set; }
        public IMerazContext Context { get; set; }
        public override string Name
        {
            get
            {
                return name;
            }
        }

        public CustomRoleProvider() {
        }
        public void Commit()
        {
            Context.Save();
        }

        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            if (name == null || name.Length == 0)
                throw new ArgumentNullException("name");
            this.name = name;
            if (config == null)
                throw new ArgumentNullException("config");
            if (config["applicationName"] == null || string.IsNullOrWhiteSpace(config["applicationName"].ToString()))
                this.ApplicationName = "MyApp";
            else
                this.ApplicationName = config["applicationName"].ToString();

            if (HttpContext.Current != null && HttpContext.Current.Items.Contains("Factory"))
            {
                IObjectFactory factory = HttpContext.Current.Items["Factory"] as IObjectFactory;
                Context = factory.GetMerazContext();
                RoleRepository = factory.GetRoleRepository();
                MemberRoleRepository = factory.GetMemberRoleRepository();
                MemberRepository = factory.GetMemberRepository();
            }
            else
            {
                Context = MerazContext.Instance;
                RoleRepository = new RoleRepository(Context);
                MemberRoleRepository = new MemberRoleRepository(Context);
                MemberRepository = new MemberRepository(Context);
            }
        }

        public override void AddUsersToRoles(string[] usernames, string[] roleNames)
        {
            if (usernames.Any(f => f == null))
                throw new ArgumentNullException("Failed because a null username was passed");
            if (roleNames.Any(f => f == null))
                throw new ArgumentNullException("Failed because a null rolename was passed");
            if (usernames.Any(f => string.IsNullOrWhiteSpace(f)))
                throw new ArgumentException("Failed because a username was an empty string");
            if (roleNames.Any(f => string.IsNullOrWhiteSpace(f)))
                throw new ArgumentException("Failed because a rolename was an empty string");

            List<string> roles = RoleRepository.Get(ApplicationName).Select(select1 => select1.RoleName).ToList();
            List<string> users = MemberRepository.GetByApplicationName(ApplicationName).Select(sel1 => sel1.UserName).ToList();

            if (!roleNames.All(f => roles.Contains(f)))
                throw new ProviderException("Failed because a rolename does not exist");
            if (!usernames.All(f => users.Contains(f)))
                throw new ProviderException("Failed because a username does not exist");

            foreach (string username in usernames)
            {
                foreach (string roleName in roleNames)
                {
                    if (IsUserInRole(username, roleName))
                        continue;
                    MemberRoleRepository.Add(new MemberRole(ApplicationName, roleName, username));
                }
            }
            Commit();
        }

        public override string ApplicationName{get;set;}

        public override void CreateRole(string roleName)
        {
            if (roleName == null)
                throw new ArgumentNullException("Failed because a null rolename was passed");
            if (string.IsNullOrWhiteSpace(roleName))
                throw new ArgumentException("Failed because an empty rolename was passed");
            if (roleName.Contains(','))
                throw new ArgumentException("Failed because rolename contains a comma");
            if (roleName.Length > 255)
                throw new ArgumentException("Failed because rolename is too long");

            if (GetAllRoles().Contains(roleName))
                return;
            RoleRepository.Add(new Role() { ApplicationName = ApplicationName, RoleName = roleName });
            Commit();
        }

        public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
        {
            if (!RoleExists(roleName))
                throw new ArgumentException(string.Format("roleName '{0}' does not exist", roleName));

            bool isPopulated = MemberRoleRepository.Get(ApplicationName).Select(sel1 => sel1.RoleName).ToList().Contains(roleName);
            
            if (throwOnPopulatedRole && isPopulated)
                throw new ApplicationException("Cannot delete populated role.");

            MemberRole[] memberRoles = MemberRoleRepository.GetByRole(ApplicationName, roleName).ToArray();
            for (int i = 0; i < memberRoles.Length; i++)
            {
                MemberRoleRepository.Delete(memberRoles[i]);
            }
            Role role = RoleRepository.Get(ApplicationName, roleName);
            if (role != null)
            {
                RoleRepository.Delete(role);
                return true;
            }
            return false;
        }

        public override string[] FindUsersInRole(string roleName, string usernameToMatch)
        {
            throw new NotImplementedException();
        }

        public override string[] GetAllRoles()
        {
            return RoleRepository.Get(ApplicationName).Select(select1 => select1.RoleName).ToArray();
        }

        public override string[] GetRolesForUser(string username)
        {
            if (username == null)
                throw new ArgumentNullException();
            if (string.IsNullOrWhiteSpace(username))
                throw new ArgumentException();
            IEnumerable<string> roles = MemberRoleRepository.Get(ApplicationName, username)
                .Select(select1 => select1.RoleName);
            return roles.ToArray();
        }

        public override string[] GetUsersInRole(string roleName)
        {
            if (roleName == null)
                throw new ArgumentNullException();
            if (string.IsNullOrWhiteSpace(roleName))
                throw new ArgumentException();
            return MemberRoleRepository.GetByRole(ApplicationName, roleName)
                .Select(select1 => select1.UserName).ToArray();
        }

        public override bool IsUserInRole(string username, string roleName)
        {
            if (roleName == null || username == null)
                throw new ArgumentNullException();
            if (string.IsNullOrWhiteSpace(roleName) || string.IsNullOrWhiteSpace(username))
                throw new ArgumentException();
            if (!RoleExists(roleName) || MemberRepository.GetByUserName(ApplicationName, username) == null)
                throw new ProviderException();

            List<MemberRole> roles = MemberRoleRepository.Get(ApplicationName, username);
            return roles.Contains(new MemberRole(){ApplicationName = ApplicationName, RoleName = roleName, UserName = username});
        }

        public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
        {
            if (!usernames.All(f => f != null) || !roleNames.All(f => f != null))
                throw new ArgumentNullException();
            if (!usernames.All(f => !string.IsNullOrWhiteSpace(f)) || !roleNames.All(f => !string.IsNullOrWhiteSpace(f)))
                throw new ArgumentException();

            List<string> roles = GetAllRoles().ToList();
            List<string> users = MemberRepository.GetByApplicationName(ApplicationName).Select(sel1 => sel1.UserName).ToList();

            if (!usernames.All(f => users.Contains(f)) || !roleNames.All(f => roles.Contains(f)))
                throw new ProviderException();

            foreach (string username in usernames)
            {
                MemberRole[] memberRoles = MemberRoleRepository.Get(ApplicationName, username).ToArray();
                for (int i = 0; i < memberRoles.Length; i++)
                {
                    if (roleNames.Contains(memberRoles[i].RoleName)){
                        MemberRoleRepository.Delete(memberRoles[i]);
                    }
                }
            }
            Commit();
        }

        public override bool RoleExists(string roleName)
        {
            if (string.IsNullOrWhiteSpace(roleName))
                throw new ArgumentNullException("roleName is not defined");

            Role role = RoleRepository.Get(ApplicationName, roleName);
            return role != null;
        }
    }
}
